package AFX::ATF;

=head1 NAME

AFX::ATF - ATF Processing

  DESTROY()                      - Destructor
  generate()                     - Generate description
  get_id()                       - Return Identifier
  get_list_argument()            - Return list of arguments
  get_list_machine()             - Return list of machines
  get_machine()                  - Return machine for specified unit
  get_static()                   - Get Step's Static
  get_step()                     - Return step for specified step's identifier
  get_step_param()               - Return Step's Parameter
  get_step_refer()               - Return Step's Referrer
  get_title()                    - Return title
  get_type()                     - Return Type
  get_version()                  - Return Version
  get_xml()                      - Return ATF as XML
  is_updated()                   - Check is ATF Updated
  message_failed_open()          - Message: ATF failed opening
  message_failed_valid()         - Message: ATF failed validation
  message_invalid_command()      - Message: Invalid command specified in ATF
  message_invalid_machine()      - Message: Invalid machine specified in ATF
  message_invalid_param()        - Message: Invalid parameter specified in ATF
  message_invalid_refer()        - Message: Template invalid for machine
  message_invalid_step()         - Message: Invalid step specified in ATF
  message_invalid_title_step()   - Message: Invalid step specified in ATF titles
  message_no_argument_template() - Message: No templates specified for Argument
  message_no_machine_param()     - Message: No parameters specified for Machine unit
  message_no_param_check()       - Message: No parameters or review specified for step
  message_no_param_template()    - Message: No templates specified for Parameter
  message_opened()               - Message: ATF opened
  message_updated()              - Message: ATF updated
  message_uptodate()             - Message: ATF up-to-date
  new()                          - Contructor
  set_static()                   - Save Step's Static
  set_version()                  - Set ATF Version
  set_xml()                      - Set XML into ATF
  update()                       - Update ATF
  validate()                     - Validate template for machine by parameters
  validate_refer()               - Validate template for machine by referrers

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2010 Mikhail Volkov

This file is part of AuditFX.

    AuditFX is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    AuditFX is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with AuditFX.  If not, see <http://www.gnu.org/licenses/>.
=cut

use strict;
use Carp;
use Exporter ();

use vars qw($VERSION);
my @ISA = qw(Exporter);
$VERSION = '1.0';

##############################################################################
#
# DESTROY()
#
# Destructor
#
# Arguments expected are:
# - self         - Object reference

sub DESTROY {
    my ($self) = @_;

## Print debug message
    print "DEBUG::Object ATF " . $self->get_id() . " destroyed\n"
      if $self->{ARF}->{Process}->is_debug();

## Return Ok
    1;

} ## end sub DESTROY

##############################################################################
#
# generate()
#
# Generate description
#
# Return:
# - 1        - if description generated
# - 0        - otherwise
#
# Arguments expected are:
# - self     - Object reference

sub generate {
    my ($self) = @_;

## Define CSS content
    my $content = "";

## Read CSS from file
    if ( open( CSS, "< lib/AFX.css" ) ) {
        $content = join( '', <CSS> );
        close(CSS);
        chomp($content);
    }

## Define HTML hash
    my $html = {
        head => {
            style => {
                type    => "text/css",
                content => $content
            },
            title => { content => "ATF " . $self->get_id() }
        },
        body => {}
    };

## Add version
    push(
        @{ $html->{body}->{p} },
        {
            class   => "title",
            content => $self->{ARF}->{Process}->{ACF}->message_title()
        }
    );

## Add TOP for linking
    push( @{ $html->{body}->{p} }, { a => { name => "top" } } );

## Add ATF/STF Identifier
    push(
        @{ $html->{body}->{p} },
        {
            class   => "site",
            content => "ATF "
              . $self->get_id() . " ["
              . $self->get_type() . "]"
              . " version "
              . $self->get_version()
        }
    );

## Get Title
    my $title = $self->get_title();

## If title not specified
    if ( !$title or !$title->{content} ) {
        $title = "No Title";
    }
    else {
        $title = $title->{content};
    }

## Add Template Title
    push( @{ $html->{body}->{p} }, { class => "machine", content => $title } );

## Add Line delimiter
    push( @{ $html->{body}->{p} }, { hr => {} } );

## Define validation steps
    my %step_valid;

## Add no machines
    if ( !$self->get_list_machine() ) {
        push(
            @{ $html->{body}->{p} },
            {
                class   => "step",
                content => "No machine units"
            }
        );
    }

## Check Machines in template
    foreach my $machine ( @{ $self->{_xml}->{machine} } ) {

## Set empty graphviz variable
        my $gv = "";

## Add valid unit
        push(
            @{ $html->{body}->{p} },
            { class => "step", content => "Valid unit: $machine->{unit}" }
        );

## Check parameters in units
        if ( !$machine->{param} and !$machine->{refer} ) {

## Print ATF unit no parameters specified message
            $self->message_no_machine_param( $machine->{unit} );
        }

## Check validation parameters for machine
        foreach my $param ( @{ $machine->{param} } ) {

## Define validation rule
            my $rule = "No rule";

## If template specified
            if ( $param->{template} and !$param->{step} ) {

## Set validation rule for template
                $rule =
"parent ATF <a href='$param->{template}.html'>$param->{template}</a>";

## Add ATF relation for graphviz
                $gv .=
                  "\"" . $self->get_id() . "\" -> \"$param->{template}\";\n";
            }

## If step specified
            if ( $param->{step} ) {

## Set validation rule for step
                $rule =
"<b><a href='#$param->{step}'>$param->{step}</a> [$param->{id}]</b>";

## Increment using step for validation
                $step_valid{ $param->{step} }++;
            }

## Check template in parameter
            if ( !$param->{template} ) {

## Print template not specified
                $self->message_no_param_template( $param->{id} );
            }

## Add validation parameter
            push(
                @{ $html->{body}->{p} },
                { pre => { content => "<u>Validation parameter</u>: $rule" } }
            );
        } ## end parameters processing

## Check referrer for machine
        foreach my $refer ( @{ $machine->{refer} } ) {

## Skip wrong referrer
            next if !$refer->{template};

## Set rule
            my $rule =
              "<a href='$refer->{template}.html'>$refer->{template}</a> ";
            $rule .= "[$refer->{content}]";

## Add ATF relation for graphviz
            $gv .= "\""
              . $self->get_id()
              . "\" -> \"$refer->{template}\" [label=$refer->{id}];\n";

## Add refer ATF
            push(
                @{ $html->{body}->{p} },
                { pre => { content => "<u>Refer ATF</u>: $rule" } }
            );
        } ## end parameters processing

## Open graphviz
        if ( open( GV, ">> template/$machine->{unit}.gv" ) ) {

## Print ATF relations
            print GV $gv;
            close(GV);
        }
    } ## end machine processing

## Add Line delimiter
    push( @{ $html->{body}->{p} }, { hr => {} } );

## Add no global arguments
    if ( !$self->get_list_argument() ) {
        push(
            @{ $html->{body}->{p} },
            {
                class   => "step",
                content => "No global arguments"
            }
        );
    }

## Check arguments in ATF
    foreach my $argument ( @{ $self->{_xml}->{argument} } ) {

## Add global argument
        push(
            @{ $html->{body}->{p} },
            {
                class   => "step",
                content => "Global argument: $argument->{id}"
            }
        );

## Set empty login
        my $login = "";

## Set login ifdefined in argument
        $login = "Login: $argument->{login}" if $argument->{login};

## Add argument's login
        push(
            @{ $html->{body}->{p} },
            {
                pre => {
                    class   => "output",
                    content => "Type: <b>$argument->{type}</b> $login"
                }
            }
        ) if $argument->{type};

## Set empty value
        my $default = "N/A";

## Set argument value if defined
        $default = $argument->{content} if $argument->{content};

## Add argument's default
        push(
            @{ $html->{body}->{p} },
            {
                pre => {
                    class   => "output",
                    content => "Default value: <b>$default</b>"
                }
            }
        );

    } ## end argument processing

## Add Line delimiter
    push( @{ $html->{body}->{p} }, { hr => {} } );

## Set global parameter count
    my $global_param = 0;

## Check global arguments in ATF
    foreach my $title ( @{ $self->{_xml}->{title} } ) {

## Skip ATF title
        next if !$title->{id} or $title->{id} eq $title->{step};

## Add title
        push(
            @{ $html->{body}->{p} },
            {
                class => "step",
                content =>
"Global parameter: <a href='#$title->{step}'>$title->{step}</a> [$title->{id}]"
            }
        );

## Add title's hint
        push(
            @{ $html->{body}->{p} },
            {
                pre => {
                    class   => "hint",
                    content => "Description: <b>$title->{content}</b>"
                }
            }
        );

## Get parameter's step in ATF
        my $step = $self->get_step( $title->{step} );

## Invalid Step
        if ( !$step or !$step->{id} ) {

## Add error into ARF
            $self->message_invalid_title_step( $title->{step} );

## Goto next;
            next;
        }

## Check parameter in step in ATF
        my $param = $self->{ARF}->get_step_param( $step, $title->{id} );

## If invalid parameter specified
        if ( !$param or !$param->{content} or $param->{content} !~ /\(.+\)/ ) {

## Add error into ARF
            $self->message_invalid_title_step( $title->{step}, $title->{id} );
        }

## Add global parameter count
        $global_param++;

    } ## end title processing

## Add no global arguments, if not defined
    push(
        @{ $html->{body}->{p} },
        {
            class   => "step",
            content => "No global parameters"
        }
    ) if !$global_param;

## Add Line delimiter
    push( @{ $html->{body}->{p} }, { hr => {} } );

## Define variables for table with steps
    my $step_validate = "";
    my $step_review   = "";
    my $step_other    = "";

## Check steps in ATF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Check validation step
        if ( $step_valid{ $step->{id} } and $step_valid{ $step->{id} } > 0 ) {
            $step_validate .= "<a href='#$step->{id}'>$step->{id}</a><br>";
        }

## Add manual review
        elsif ( $step->{review} and $step->{review} eq 'true' ) {
            $step_review .= "<a href='#$step->{id}'>$step->{id}</a><br>";
        }

## Add daemon daily
        elsif ( $step->{daemon} and $step->{daemon} eq 'daily' ) {
            $step_other .=
              "<a href='#$step->{id}'>$step->{id}</a> <b>[daily]</b><br>";
        }

## Add daemon weekly
        elsif ( $step->{daemon} and $step->{daemon} eq 'weekly' ) {
            $step_other .=
              "<a href='#$step->{id}'>$step->{id}</a> <b>[weekly]</b><br>";
        }

## Add other steps
        else {
            $step_other .= "<a href='#$step->{id}'>$step->{id}</a><br>";
        }
    }

## Define step's table
    my $step_table = "<table><tr class=header><td class=error>Validation</td>"
      . "<td class=review>Manual review</td><td>Daemon/Other</td></tr>";
    $step_table .= "<tr><td class=error>$step_validate</td>";
    $step_table .= "<td class=review>$step_review</td>";
    $step_table .= "<td>$step_other</td></tr></table>";

## Add step's table in XHTML
    push( @{ $html->{body}->{p} }, { content => $step_table } );

## Add Line delimiter
    push( @{ $html->{body}->{p} }, { hr => {} } );

## Check steps in ATF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Add Step's name for link
        push( @{ $html->{body}->{p} }, { a => { name => $step->{id} } } );

## Add step's title
        push(
            @{ $html->{body}->{p} },
            { class => "step", content => "Step: $step->{id}" }
        );

## Format Hint for step
        my $hint      = "Hint: ";
        my $step_hint = $self->{ARF}->get_step_hint($step);

## Set Hint
        if (   !$step_hint
            or !$step_hint->{content}
            or $step_hint->{content} =~ /[^[:ascii:]]/ )
        {

## Print no hint error
            print $self->{ARF}->message_no_hint( $self->get_id(), $step->{id} )
              . "\n";

## Add no hint
            $hint .= "<b>" . $self->{ARF}->message_no_hint() . "</b>";
        }
        else {

## Set hint content
            $hint .= $step_hint->{content};
        }

## Add step's hint
        push(
            @{ $html->{body}->{p} },
            { pre => { class => "hint", content => $hint } }
        );

## Check arguments in Step
        foreach my $argument ( @{ $step->{argument} } ) {

## Set empty redefined step argument
            my $step = "";

## Check is argument redefined by parameters
            $step .= "as <b>$argument->{step} [$argument->{content}]</b> "
              if $argument->{step} and $argument->{content};

## Check is argument specified from template
            $step .= "from ATF <b>$argument->{template}</b> "
              if $argument->{template};

## Check is argument specified for units
            $step .= "for MACH <b>$argument->{unit}</b> "
              if $argument->{unit};

## Check template in step
            if ( !$argument->{template} and !$argument->{unit} ) {

## Print template not specified
                $self->message_no_argument_template( $argument->{id} );
            }

## Add command
            push(
                @{ $html->{body}->{p} },
                {
                    class => "node",
                    pre   => {
                        content =>
                          "<u>Argument</u>: <b>$argument->{id}</b> $step"
                    }
                }
            );

        } ## end argument processing

## Get command from ARF
        my $step_cmd = $self->{ARF}->get_step_command( $step, 0, 0, 1 );

## Check command for wide characters
        if ( !$step_cmd or $step_cmd->{content} =~ /[^[:ascii:]]/ ) {

## Print error message
            print $self->message_invalid_command( $step->{id} );

## Goto next step
            next;
        }

## Add command
        push(
            @{ $html->{body}->{p} },
            {
                class   => "node",
                content => "=> $step_cmd->{content}"
            }
        );

## Add command's login
        push(
            @{ $html->{body}->{p} },
            {
                pre => {
                    class   => "output",
                    content => "Login: <b>$step_cmd->{login}</b>"
                }
            }
        );

## if command's timeout specified
        if ( $step_cmd->{timeout} ) {

## Add command's timeout
            push(
                @{ $html->{body}->{p} },
                {
                    pre => {
                        class   => "output",
                        content => "Timeout: <b>$step_cmd->{timeout}</b>"
                    }
                }
            );
        }

## if command's end specified
        if ( $step_cmd->{end} ) {

## Add command's end
            push(
                @{ $html->{body}->{p} },
                {
                    pre => {
                        class   => "output",
                        content => "Expected end: <b>$step_cmd->{end}</b>"
                    }
                }
            );
        }

## if remote command specified
        if ( $step_cmd->{remote} ) {

## Add command's as remote
            push(
                @{ $html->{body}->{p} },
                {
                    pre => {
                        class => "output",
                        content =>
                          "Remote: <b>Will be executed on remote node</b>"
                    }
                }
            );
        }

## Check referrer in Step
        foreach my $refer ( @{ $step->{refer} } ) {

## Set referrer link
            my $refer_link =
"<a href='$refer->{template}.html#$refer->{step}'>$refer->{step}</a>"
              . " [$refer->{content}]";

## Set if referrer on remote node
            $refer_link .= " on remote node"
              if $refer->{remote} and $refer->{remote} eq 'true';

## Add command
            push(
                @{ $html->{body}->{p} },
                {
                    class => "node",
                    pre   => { content => "<u>Referrer</u>: $refer_link" }
                }
            );
        } ## end referrer processing

## Add no parameters specified
        if ( !$step->{param} and !$step->{review} ) {

            push(
                @{ $html->{body}->{p} },
                {
                    class   => "step_error",
                    content => $self->message_no_param_check( $step->{id} )
                }
            );
        }

## Check parameters in Step
        foreach my $hint ( @{ $step->{hint} } ) {

## Skip step's hint
            next if !$hint->{id};

## Check parameter in step in ATF
            my $param = $self->{ARF}->get_step_param( $step, $hint->{id} );

## Skip parameter for template validation
            next if $param and $param->{template};

## If Parameter's hint specified use it
            if ( !$param or !$param->{content} ) {

## Print Error if no Hint exists
                print $self->{ARF}
                  ->message_no_param( $self->get_id(), $step->{id},
                    $hint->{id} )
                  . "\n";
            }

## Check hint for wide characters
            if ( $hint->{content} =~ /[^[:ascii:]]/ ) {

## Print error message
                print $self->message_invalid_param( $hint->{id} );
            }
        } ## end hints processing

## Check parameters in Step
        foreach my $param ( @{ $step->{param} } ) {

## Define Parameter attributes
            my $param_attr = "";

## Show referrer parameter
            $param_attr .= "[Referrer only]"
              if $param->{severity} and $param->{severity} eq 'refer';

## Show nodes compare parameter
            $param_attr .= "[Nodes compare]"
              if $param->{compare} and $param->{compare} eq 'node';

## Add parameter
            push(
                @{ $html->{body}->{p} },
                {
                    class => "node",
                    pre   => {
                        content =>
                          "<u>Parameter</u>: <b>$param->{id}</b> $param_attr"
                    }
                }
            );

## Check parameter's hint
            my $param_hint = $self->{ARF}->get_step_hint( $step, $param->{id} );

## If Parameter's hint specified use it
            if ( $param_hint and $param_hint->{content} ) {

## Set hint content
                $param_hint = $param_hint->{content};
            }
            else {

## Set no hint error
                $param_hint = "<b>" . $self->{ARF}->message_no_hint() . "</b>";

## Print Error if no Hint exists
                print $self->{ARF}
                  ->message_no_hint( $self->get_id(), $step->{id},
                    $param->{id} )
                  . "\n";
            }

## Add parameters's hint
            push(
                @{ $html->{body}->{p} },
                {
                    pre => {
                        class   => "hint",
                        content => "Hint: $param_hint"
                    }
                }
            );

## If template specified
            if ( $param->{template} ) {

## Add parameter's method
                push(
                    @{ $html->{body}->{p} },
                    {
                        pre => {
                            class => "output",
                            content =>
"Validate ATF: <a href='$param->{template}.html'>$param->{template}</a>"
                        }
                    }
                );

## Goto next parameter
                next;
            }

## Set empty method
            my $method = "";

## Describe method
            if ( $param->{method} eq '=~' ) {
                $method = "[Output <b>should</b> match rule]";
            }
            elsif ( $param->{method} eq '!~' ) {
                $method = "[Output <b>shouldn't</b> match rule]";
            }
            else {
                $method = "[<b class=failed>Unknown</b> rule]";
            }

## Add parameter's method
            push(
                @{ $html->{body}->{p} },
                {
                    pre => {
                        class   => "output",
                        content => "Method: $param->{method} $method"
                    }
                }
            );

## Add parameter's rule
            push(
                @{ $html->{body}->{p} },
                {
                    pre => {
                        class   => "output",
                        content => "Rule: <b>$param->{content}</b>"
                    }
                }
            );

## Try to use rule
            eval { $param->{id} =~ /$param->{content}/; };

## If error within Regex occured
            if ($@) {

## Add message into ARF
                $self->message_invalid_param( $step->{id}, $param->{id} );
            }

        } ## end parameters processing

## Add Back and Top in Step
        push(
            @{ $html->{body}->{p} },
            {
                content =>
                  "<A href='javascript:history.back(1)'>Back</A>&nbsp;&nbsp;|"
                  . "&nbsp;&nbsp;<a href='#top'>Top</a>"
            }
        );

## Add Line delimiter
        push( @{ $html->{body}->{p} }, { hr => {} } );
    }    ## step processing

## Generate HTML description
    eval {
        $self->{ARF}->{Process}->{ACF}->{xs}->XMLout(
            $html,
            RootName   => 'html',
            OutputFile => $self->{directory} . "/" . $self->get_id() . ".html",
            NoEscape   => 1
        );
    };

## Return Ok
    return 1;

} ## end sub generate

##############################################################################
#
# get_id()
#
# Return ATF Identifier
#
# Return:
# - identifier
#
# Arguments expected are:
# - self    - Object reference

sub get_id {
    my ($self) = @_;

## Return Identifier
    return $self->{_xml}->{id};

} ## end sub get_id

##############################################################################
#
# get_list_argument()
#
# Return:
# - list of arguments
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_argument {
    my ($self) = @_;

## Return Arguments list
    return $self->{_xml}->{argument}
      if $self->{_xml}->{argument}
          and @{ $self->{_xml}->{argument} };

## Return Error
    return 0;

} ## end sub get_list_argument

##############################################################################
#
# get_list_machine()
#
# Return:
# - list of machines
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_machine {
    my ($self) = @_;

## Return Machines list
    return $self->{_xml}->{machine}
      if $self->{_xml}->{machine}
          and @{ $self->{_xml}->{machine} };

## Return Error
    return 0;

} ## end sub get_list_machine

##############################################################################
#
# get_machine()
#
# Return ATF machine for specified unit
#
# Return:
# - Machine hash  - if exists in ATF
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - unit          - Machine's unit

sub get_machine {
    my ( $self, $unit ) = @_;

## Return error, if no machines specified
    return 0 if !$self->get_list_machine();

## Check machine in ATF
    foreach my $machine ( @{ $self->get_list_machine() } ) {

## Skip wrong machine
        next if $machine->{unit} ne $unit;

## Return Machine's Hash
        return $machine;
    }

## If machine not found, return Error
    return 0;

} ## end sub get_machine

##############################################################################
#
# get_static()
#
# Get Step's Static
#
# Return:
# - content       - if static found successfully
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - step_id       - Step's Identifier

sub get_static {
    my ( $self, $step_id ) = @_;

## Get directory
    my $atfdir =
      $self->{ARF}->{Process}->{ACF}->get_directory( $self->{directory} );

## Set full pathname
    my $path = $atfdir . "/static/" . $self->get_id() . "." . $step_id;

## Open Static
    if ( -f $path and open( STATIC, "< $path" ) ) {

## Get Static content
        my $content = join( '', <STATIC> );
        close(STATIC);
        chomp($content);

## Return Static
        return $content;
    }

## Return Nothing
    return "";

} ## end sub get_static

##############################################################################
#
# get_step()
#
# Return ATF step for specified step's identifier
#
# Return:
# - Step hash     - if it exists in ATF
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - step_id       - Step's id

sub get_step {
    my ( $self, $step_id ) = @_;

## Check steps in ATF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Skip wrong step
        next if $step->{id} ne $step_id;

## Return Step Hash
        return $step;
    }

## If step not found, return Error
    return 0;

} ## end sub get_step

##############################################################################
#
# get_step_param()
#
# Return Step's Parameter
#
# Return:
# - Param hash   - if it exists in Step
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - step         - Step hash
# - param_id     - Parameter's id

sub get_step_param {
    my ( $self, $step, $param_id ) = @_;

## Check parameters in step
    foreach my $param ( @{ $step->{param} } ) {

## Skip wrong parameter
        next if $param->{id} ne $param_id;

## Return Parameter hash
        return $param;
    }

## If parameter doesn't exist in step, return error
    return 0;

} ## end sub get_step_param

##############################################################################
#
# get_step_refer()
#
# Return Step's Referrer
#
# Return:
# - Refer hash      - if it exists in Step
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - step            - Step hash
# - refer_template  - Refer's Template
# - refer_step      - Refer's Step
# - refer_param     - Refer's Parameter

sub get_step_refer {
    my ( $self, $step, $refer_template, $refer_step, $refer_param ) = @_;

## Check referrer in step
    foreach my $refer ( @{ $step->{refer} } ) {

## Skip wrong referrer
        next if $refer->{template} ne $refer_template;
        next if $refer->{step}     ne $refer_step;
        next if $refer->{content}  ne $refer_param;

## Return Referrer hash
        return $refer;
    }

## If referrer doesn't exist in step, return error
    return 0;

} ## end sub get_step_refer

##############################################################################
#
# get_title()
#
# Return ATF title
#
# Return:
# - Title hash   - if exists in ATF
# - 0            - otherwise
#
# Arguments expected are:
# - self        - Object reference

sub get_title {
    my ($self) = @_;

## Check title in ATF
    foreach my $title ( @{ $self->{_xml}->{title} } ) {

## Return Title for ATF
        return $title if !$title->{id};
    }

## If title not specified in ATF, return Error
    return 0;

} ## end sub get_title

##############################################################################
#
# get_type()
#
# Return ATF Type
#
# Return:
# - type
#
# Arguments expected are:
# - self    - Object reference

sub get_type {
    my ($self) = @_;

## Return Type
    return $self->{_xml}->{type};

} ## end sub get_type

##############################################################################
#
# get_version()
#
# Return ATF Version
#
# Return:
# - version
#
# Arguments expected are:
# - self    - Object reference

sub get_version {
    my ($self) = @_;

## Return Version
    return $self->{_xml}->{version};

} ## end sub get_version

##############################################################################
#
# get_xml()
#
# Return ATF as XML
#
# Return:
# - xml
#
# Arguments expected are:
# - self    - Object reference

sub get_xml {
    my ($self) = @_;

## Return ATF
    return eval {
        $self->{ARF}->{Process}->{ACF}->{xs}
          ->XMLout( $self->{_xml}, RootName => 'template' );
    };

} ## end sub get_xml

##############################################################################
#
# is_updated()
#
# Check is ATF Updated
#
# Return:
# - updated state
#
# Arguments expected are:
# - self    - Object reference

sub is_updated {
    my ($self) = @_;

## Return updated state
    return $self->{updated};

} ## end sub is_updated

##############################################################################
#
# message_failed_open()
#
# Message: ATF failed opening
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference
# - error    - Error

sub message_failed_open {
    my ( $self, $error ) = @_;

## Set message
    my $message = "ATF $self->{file}: failed opening.";
    $message .= "\nError: $error" if $error;

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_open

##############################################################################
#
# message_failed_valid()
#
# Message: ATF failed validation
#
# Return:
# - logged message
#
# Arguments expected are:
# - self       - Object reference
# - error      - Error

sub message_failed_valid {
    my ( $self, $error ) = @_;

## Remove newlines in error
    $error =~ s/\n//g;

## Set message
    my $message = "ATF $self->{file}: failed validation.";
    $message .= "\nError: $error" if $error;

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_valid

##############################################################################
#
# message_invalid_command()
#
# Message: Invalid command specified in ATF
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - step_id   - Step's Id

sub message_invalid_command {
    my ( $self, $step_id ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "command of $step_id specified incorrectly";

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_command

##############################################################################
#
# message_invalid_machine()
#
# Message: Invalid machine specified in ATF
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - mach_id   - Machine's Id

sub message_invalid_machine {
    my ( $self, $mach_id ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "$mach_id unit is not supported";

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_machine

##############################################################################
#
# message_invalid_param()
#
# Message: Invalid parameter specified in ATF
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - step_id   - Step's Id
# - param_id  - Parameter's Id

sub message_invalid_param {
    my ( $self, $step_id, $param_id ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "parameter $param_id of $step_id specified incorrectly";

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_param

##############################################################################
#
# message_invalid_refer()
#
# Message: Template invalid for machine
#
# Return:
# - logged message
#
# Arguments expected are:
# - self       - Object reference
# - refer      - ATF referrer
# - machine_id - Machine's Identifier

sub message_invalid_refer {
    my ( $self, $refer, $machine_id ) = @_;

## Set message
    my $message = "ATF Validation for $refer on $machine_id failed. ";
    $message .= "Check invalid parameters.";

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $message;

} ## end sub message_invalid_refer

##############################################################################
#
# message_invalid_step()
#
# Message: Invalid step specified in ATF
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - step_id   - Step's Id

sub message_invalid_step {
    my ( $self, $step_id ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "invalid step $step_id specified";

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_step

##############################################################################
#
# message_invalid_title_step()
#
# Message: Invalid step specified in ATF titles
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - step_id   - Step's Id
# - param_id  - Parameter's Id

sub message_invalid_title_step {
    my ( $self, $step_id, $param_id ) = @_;

## Set step's output
    my $step = $step_id;

## Add parameter into step's output
    $step .= "[$param_id]" if $param_id;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "invalid title for $step specified";

## Return Message, if error already added into ARF
    return $message if !$self->{ARF}->add_error($message);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_title_step

##############################################################################
#
# message_no_argument_template()
#
# Message: No templates specified for Argument
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - argument_id  - Argument's Id

sub message_no_argument_template {
    my ( $self, $argument_id ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "no template specified for argument $argument_id";

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_no_argument_template

##############################################################################
#
# message_no_machine_param()
#
# Message: No parameters specified for Machine unit
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - unit      - Unit's Id

sub message_no_machine_param {
    my ( $self, $unit ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "no parameters or referrers specified for unit $unit";

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_no_machine_param

##############################################################################
#
# message_no_param_check()
#
# Message: No parameters or review specified for step
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - step_id      - Step's Id

sub message_no_param_check {
    my ( $self, $step_id ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "parameters/review aren't specified for step $step_id";

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_no_param_check

##############################################################################
#
# message_no_param_template()
#
# Message: No templates specified for Parameter
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - param_id     - Parameter's Id

sub message_no_param_template {
    my ( $self, $param_id ) = @_;

## Set message
    my $message = "ATF " . $self->get_id() . ": ";
    $message .= "no template specified for validation parameter $param_id";

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_no_param_template

##############################################################################
#
# message_opened()
#
# Message: ATF opened
#
# Return:
# - logged message
#
# Arguments expected are:
# - self       - Object reference

sub message_opened {
    my ($self) = @_;

## Set message
    my $message = "ATF "
      . get_id($self)
      . ": version "
      . get_version($self)
      . ", type "
      . get_type($self);

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 0, 'audit' );

} ## end sub message_opened

##############################################################################
#
# message_updated()
#
# Message: ATF updated
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference

sub message_updated {
    my ($self) = @_;

## Set message
    my $message = "ATF $self->{file}: updated successfully";

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_updated

##############################################################################
#
# message_uptodate()
#
# Message: ATF up-to-date
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference

sub message_uptodate {
    my ($self) = @_;

## Set message
    my $message = "$self->{id}: up-to-date";

## Return message
    return $self->{ARF}->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_uptodate

##############################################################################
#
# new()
#
# ATF contructor
#
# Arguments expected are:
# - ARF Object
# - Template's filename or Identifier
# - Template's Directory

sub new {

## Get package name
    my $type = shift;

## Define object's hash
    my $self = { ARF => shift, id => shift, directory => shift, updated => 0 };

## If specified Template's Identifier
    if ( $self->{id} !~ /\.xml$/ ) {
        $self->{file} = $self->{id} . ".xml";
    }
    else {
        $self->{file} = $self->{id};
    }

## Get directory
    my $atfdir =
      $self->{ARF}->{Process}->{ACF}->get_directory( $self->{directory} );

## Set full pathname
    $self->{path} = $atfdir . "/" . $self->{file};

## If failed opening, return 0
    if ( !-f $self->{path} ) {

## Print ATF failed openning
        message_failed_open( $self, "$@" );

## Return Error
        return 0;
    }

## Validate ATF
    my $error = $self->{ARF}->{Process}->{ACF}->validate_file( $self->{path} );

## If ATF failed validation, return Error
    if ($error) {

## Print ATF failed validation
        message_failed_valid( $self, $error );

## Return Error
        return 0;
    }

## Open ATF
    $self->{_xml} =
      eval { $self->{ARF}->{Process}->{ACF}->{xs}->XMLin( $self->{path} ); };

## If ATF failed openning return 0
    if ( $@ or !$self->{_xml} or !$self->{_xml}->{id} ) {

## Print ATF failed openning
        message_failed_open( $self, "$@" );

## Return Error
        return 0;
    }

## Print ATF opened message
    message_opened($self) if $self->{ARF}->{Process}->is_debug();

## Redefine Template's Identifier in Object
    $self->{id} = get_id($self);

## Check arguments in ATF
    foreach my $argument ( @{ $self->{_xml}->{argument} } ) {

## Add argument into ARF
        $self->{ARF}->add_argument( $argument, get_id($self) );
    }

## Check titles in ATF
    foreach my $title ( @{ $self->{_xml}->{title} } ) {

## Add title into ARF
        $self->{ARF}->add_title( $title, get_id($self) );
    }

## Print debug message
    print "DEBUG::Object ATF " . get_id($self) . " created\n"
      if $self->{ARF}->{Process}->is_debug();

## Return object
    return bless $self, $type;

} ## end sub new

##############################################################################
#
# set_static()
#
# Save Step's Static
#
# Return:
# - 1             - if set successfully
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - step_id       - Step's Identifier
# - content       - Static's content

sub set_static {
    my ( $self, $step_id, $content ) = @_;

## Return Error if step wasn't specified
    return 0 if !$step_id;

## Get directory
    my $atfdir =
      $self->{ARF}->{Process}->{ACF}->get_directory( $self->{directory} );

## Set full pathname
    my $path = $atfdir . "/static/" . $self->get_id() . "." . $step_id;

## Open Static
    if ( !open( STATIC, "> $path" ) ) {

## Return Error
        return 0;
    }

## Set Static's content
    print STATIC $content;

## Close Static
    close(STATIC);

## Return Ok
    return 1;

} ## end sub set_static

##############################################################################
#
# set_version()
#
# Set ATF Version
#
# Return:
# - 1
#
# Arguments expected are:
# - self    - Object reference

sub set_version {
    my ($self) = @_;

## Set Version
    $self->{_xml}->{version} = $self->{ARF}->{Process}->{ACF}->get_version();

## Return Ok
    return 1;

} ## end sub set_version

##############################################################################
#
# set_xml()
#
# Set XML into ATF
#
# Return:
# - 1       - if xml set
# - 0       - if error happend
#
# Arguments expected are:
# - self    - Object reference
# - xml     - XML

sub set_xml {
    my ( $self, $xml ) = @_;

## Validate ATF
    my $error = $self->{ARF}->{Process}->{ACF}->validate_string($xml);

## If ATF failed validation, return Error
    if ($error) {

## Print ATF failed validation
        message_failed_valid( $self, $error );

## Return Error
        return 0;
    }

## Open ATF
    $self->{_xml} = eval { $self->{ARF}->{Process}->{ACF}->{xs}->XMLin($xml); };

## If ATF failed openning return 0
    if ( $@ or !$self->{_xml} or !$self->{_xml}->{id} ) {

## Print ATF failed openning
        message_failed_open( $self, "$@" );

## Return Error
        return 0;
    }

## Return Ok
    return 1;

} ## end sub set_xml

##############################################################################
#
# update()
#
# Update ATF
#
# Return:
# - 1         - if updated sucessfully
# - 0         - otherwise
#
# Arguments expected are:
# - self      - Object reference

sub update {
    my ($self) = @_;

## If ATF not Updated
    if ( !$self->{updated} ) {

## Print message: ATF up-to-date
        $self->message_uptodate();

## Return Error
        return 0;
    }

## Set version
    $self->set_version();

## Save ATF
    eval {
        $self->{ARF}->{Process}->{ACF}->{xs}->XMLout(
            $self->{_xml},
            RootName   => 'template',
            OutputFile => $self->{path}
        );
    };

## Validate ATF
    my $error = $self->{ARF}->{Process}->{ACF}->validate_file( $self->{path} );

## Exit if ATF failed validation
    if ($error) {

## Print failed validation error
        $self->message_failed_valid($error);

## Return Error
        return 0;
    }

## Empty update
    $self->{updated} = 0;

## Print ATF created message
    $self->message_updated();

## Return Ok
    return 1;

} ## end sub update

##############################################################################
#
# validate()
#
# Validate template for machine by parameters
#
# Return:
# - 1          - if ATF valid
# - 0          - otherwise
#
# Command arguments expected are:
# - self       - Object reference
# - node_id    - Node's Identifier

sub validate {
    my ( $self, $node_id ) = @_;

## Parse Machine from ATF
    $self->{_machine} =
      $self->get_machine( $self->{ARF}->get_machine_unit($node_id) );

## if ATF not for this machine
    if ( !$self->{_machine} ) {

## Add error in ARF about invalid machine
        $self->message_invalid_machine(
            $self->{ARF}->get_machine_unit($node_id) );

## return Error
        return 0;
    }

## Get Machine
    my $arf_machine = $self->{ARF}->get_machine($node_id);

## Check template
    my $template = $self->{ARF}->get_machine_template( $node_id, $self->{id} );

## Return Ok if template already validated
    return 1
      if $template
          and $template->{valid}
          and $template->{valid} eq 'true';

## Check parameters in Machine
    foreach my $param ( @{ $self->{_machine}->{param} } ) {

## If referrer ATF specified
        if ( $param->{template} and !$param->{step} ) {

## Open ATF
            my $atf =
              new AFX::ATF( $self->{ARF}, $param->{template}, 'template' );

## Goto next parameter if ATF valid
            next if $atf and $atf->validate($node_id);

## If validated template invalid
            if ( $self->{ARF}
                ->get_machine_template( $node_id, $param->{template} ) )
            {

## Add error in ARF about referrer ATF
                $self->message_invalid_refer( $param->{template},
                    $arf_machine->{id} );
            }

## Return Error if ATF not valid
            return 0;
        }

## Get parameter's step in ATF
        my $step = $self->get_step( $param->{step} );

## If step from ATF
        if ($step) {

## Set Step template if it not specified in ATF
            $step->{template} = $self->get_id();
        }
        else {

## Get parameter's step in ARF
            $step =
              $self->{ARF}->get_step( $param->{step}, $param->{template} );
        }

## Exception: Invalid Step
        if ( !$step or !$step->{id} ) {

## Add error into ARF
            $self->message_invalid_step( $param->{step} );

## Return error
            return 0;
        }    ## Exception

## Flag for node's param validation
        my $node_valid = 0;

## Check all sessions
        foreach my $node ( @{ $arf_machine->{node} } ) {

## Check parameter in step in ATF
            my $step_param =
              $self->{ARF}
              ->get_step_param( $step, $param->{id}, $node->{host} );

## Exception: Invalid Step
            if ( !$step_param ) {

## Add error into ARF
                $self->message_invalid_step( $param->{step} );

## Return error
                return 0;
            }    ## Exception

## Execute step for node
            my $arf_step = $self->{ARF}->execute_step( $step, $node->{host} );

## Skip session if error happends while command execution
            next if !$arf_step;

## Check valid parameter
            my $valid_param =
              $self->{ARF}
              ->check_node_param( $arf_step, $param->{id}, $node->{host} );

## Next If parameter not valid
            next if !$valid_param or $valid_param->{value} != 1;

## Increment parameter validation flag
            $node_valid++;
        }

## Goto parameter if parameter validated for any node
        next if $node_valid;

## If validated template invalid
        if ( $self->{ARF}->get_machine_template( $node_id, $self->get_id() ) ) {

## Add error in ARF about referrer ATF
            $self->message_invalid_refer( $self->get_id(), $arf_machine->{id} );
        }

## Return Error
        return 0;
    }

## Print debug message
    print "DEBUG::Object ATF " . $self->get_id() . " validated\n"
      if $self->{ARF}->{Process}->is_debug();

## Add validated template into Machine
    $self->{ARF}
      ->add_machine_template( $node_id, $self->get_id(), $self->get_type(),
        $self->get_version() );

## If all parameters validated sucessfully, Return Ok
    return 1;

} ## end sub validate

##############################################################################
#
# validate_refer()
#
# Validate template for machine by referrers
#
# Return:
# - 1          - if ATF's referrers checked
# - 0          - otherwise
#
# Command arguments expected are:
# - self       - Object reference
# - node_id    - Node's Identifier

sub validate_refer {
    my ( $self, $node_id ) = @_;

## Get Machine
    my $machine = $self->{ARF}->get_machine($node_id);

## if Machine doesn't exist, return Error
    return 0 if !$machine;

## Parse Machine from ATF
    $self->{_machine} = $self->get_machine( $machine->{unit} );

## if ATF not for this machine, return Error
    return 0 if !$self->{_machine};

## Set invalid validation group
    my %refgroup = {};

## Check referrer's in Machine
    foreach my $refer ( @{ $self->{_machine}->{refer} } ) {

## Print debug message
        print
"DEBUG::ATF check validation tree for $refer->{template} [$refer->{id}]\n"
          if $self->{ARF}->{Process}->is_debug();

## Skip if referred ATF not specified
        next if !$refer->{template} or $refgroup{ $refer->{id} };

## Template validation flag
        my $valid = 0;

## Check referrer within referrer's group in Machine
        foreach my $refer_cur ( @{ $self->{_machine}->{refer} } ) {

## Skip referrer with different id
            next if $refer->{id} ne $refer_cur->{id};

## Skip if template not validated
            next
              if !$self->{ARF}
                  ->get_machine_template( $node_id, $refer_cur->{template} );

## Increment validated flag
            $valid++;
        }

## Goto next referrer if valid
        next if $valid and !$refer->{multiple};

## Try to validate referrer within referrer's group in Machine
        foreach my $refer_cur ( @{ $self->{_machine}->{refer} } ) {

## Skip referrer with different id
            next if $refer->{id} ne $refer_cur->{id};

## Open ATF
            my $atf =
              new AFX::ATF( $self->{ARF}, $refer_cur->{template}, 'template' );

## If ATF valid
            $atf->validate($node_id) if $atf;

## Skip if template not validated
            next
              if !$self->{ARF}
                  ->get_machine_template( $node_id, $refer_cur->{template} );

## Increment validated flag
            $valid++;
        }

## If no templates valid
        if ( !$valid ) {

## Add error in ARF about referrer ATF
            $self->message_invalid_refer( $refer->{content}, $machine->{id} );

## Set group as invalid
            $refgroup{ $refer->{id} }++;

## Print debug message
            print "DEBUG::ATF Validation group $refer->{id} wasn't validated\n"
              if $self->{ARF}->{Process}->is_debug();

## Goto next referrer group
            next;
        }

## Set review for non-mastered steps within referrer's group in Machine
        foreach my $refer_cur ( @{ $self->{_machine}->{refer} } ) {

## Skip referrer with different id
            next if $refer->{id} ne $refer_cur->{id};

## Skip if template not validated
            next
              if $self->{ARF}
                  ->get_machine_template( $node_id, $refer_cur->{template} );

## Check steps in ATF
            foreach my $step ( @{ $self->{ARF}->{_xml}->{step} } ) {

## Skip wrong step's templates
                next if $step->{template} ne $refer_cur->{template};

## Set review flag as false
                $step->{review} = 'false';
            }
        }
    }

## Return Ok
    return 1;

} ## end sub validate_refer

1;
